/*
 * Galaxium Messenger
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;

using Mono.Addins;
using Mono.Nat;
using Mono.Nat.Pmp;
using Mono.Nat.Upnp;

using Galaxium.Core;
using Anculus.Core;

namespace Galaxium.Protocol
{
	public delegate void PortCallback (int port);
	
	//TODO: a method to 'guess' a free port, based on the settings or a random port
	//TODO: allocate port maps ahead of time?
	public static class NetworkUtility
	{
		public static event EventHandler DiscoveryCompleted;
		
		private static IPAddress _ipAddress;
		private static bool _discoveryComplete = false;
		private static INatDevice _device;
		
		private static Dictionary<int, Mapping> _mappings;
		
		public static void Initialize ()
		{
			_mappings = new Dictionary<int,Mapping> ();

			try
			{
				IPAddress[] addresses = NatUtility.GetLocalAddresses (false);
				
				NatUtility.AddController (new UpnpNatController (addresses));
				NatUtility.AddController (new PmpNatController (addresses));
				
				NatUtility.DeviceFound += DeviceFound;
				NatUtility.DeviceLost += DeviceLost;
				
				_discoveryComplete = false;
				
				NatUtility.StartDiscovery ();
				
				Log.Info ("NAT service discovery started.");
			}
			catch (Exception ex)
			{
				Log.Error (ex, "Error starting NAT discovery service");
			}
		}
		
		public static IPAddress IPAddress
		{
			get { return _ipAddress; }
		}
		
		public static bool UseNAT
		{
			get { return _device != null; }
		}
		
		public static bool DiscoveryComplete
		{
			get { return _discoveryComplete; }
		}
		
		public static bool CreatePortMap (int port, out int externalPort)
		{
			if (!_discoveryComplete) {
				externalPort = -1;
				return false;
			}
			
			try {
				Mapping mapping = new Mapping (Mono.Nat.Protocol.Tcp, port, port);
				_device.CreatePortMap (mapping);
				
				if (_mappings.ContainsKey (mapping.PrivatePort))
					_mappings[mapping.PrivatePort] = mapping;
				else
					_mappings.Add (mapping.PrivatePort, mapping);
				
				externalPort = mapping.PublicPort;
				Log.Info ("NAT mapping to port [{0}] has been created.", port);
				return true;
			} catch (Exception e) {
				Log.Error (e, "NAT mapping to port [{0}] could not be created.", port);
				externalPort = -1;
				return false;
			}
		}
		
		public static void DeletePortMap (int port)
		{
			if (!_discoveryComplete)
				return;
			
			Mapping mapping = null;
			if (_mappings.TryGetValue (port, out mapping)) {
				try {
					_device.DeletePortMap (mapping);
					Log.Info ("NAT mapping to port [{0}] has been deleted.", port);
				} catch (Exception e) {
					Log.Error (e, "NAT mapping to port [{0}] could not be deleted.", port);
				}
			}
		}
		
		private static void DeviceFound (object sender, DeviceEventArgs args)
		{
			_device = args.Device;
			_ipAddress = _device.GetExternalIP ();
			
			Log.Info ("NAT device found! Type: "+_device.NatController.Name+", IP: "+_ipAddress);
			NatUtility.StopDiscovery ();
			Log.Info ("NAT discovery stopped.");
			
			if (DiscoveryCompleted != null)
				DiscoveryCompleted (null, new EventArgs ());
			
			_discoveryComplete = true;
		}
		
		private static void DeviceLost (object sender, DeviceEventArgs args)
		{
			_device = null;
			
			Log.Error ("NAT device lost! Type: {0}", args.Device.NatController.Name);
			
			_discoveryComplete = false;
		}
	}
}